﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LumiSoft.Net.IMAP;
using LumiSoft.Net.IMAP.Client;
using LumiSoft.Net;
using System.Configuration;
using System.IO;
using System.Threading;

namespace uTorrentRemote.TokenSources.Imap
{
    class ImapWrapper: IDisposable
    {
        private abstract class WrapperStep
        {
            protected IMAP_Client client;
            public WrapperStep(IMAP_Client client)
            {
                this.client = client;
            }
        }

        private sealed class WrapperStep1 : WrapperStep
        {
            public WrapperStep1(IMAP_Client client, string subject, int[] unseen_ids)
                : base(client)
            {
                this.subject = subject;
                this.unseen_ids = unseen_ids;
            }

            private int countToLoad = 0;
            private List<int> result = null;
            private string subject = null;
            private int[] unseen_ids;

            private int currentIndex = 0;

            public int[] GetIdsWithSubject()
            {
                currentIndex = 0;

                countToLoad = unseen_ids.Count();
                result = new List<int>();

                IMAP_Client_FetchHandler fetchHandler = new IMAP_Client_FetchHandler();
                fetchHandler.Envelope += new EventHandler<EventArgs<IMAP_Envelope>>(fetchHandler_Envelope);
                fetchHandler.NextMessage += new EventHandler(fetchHandler_NextMessage);

                IMAP_SequenceSet sequence = new IMAP_SequenceSet();
                sequence.Parse(string.Join(",", unseen_ids));

                // fetch messages now
                // IMAP_Fetch_DataItem_Envelope - load message header only
                client.Fetch(false, sequence, new IMAP_Fetch_DataItem[] { new IMAP_Fetch_DataItem_Envelope() }, fetchHandler);

                while(countToLoad > 0)
                {
                    Thread.Sleep(500);
                }

                return result.ToArray();
            }

            void fetchHandler_NextMessage(object sender, EventArgs e)
            {
                currentIndex++;
            }

            void  fetchHandler_Envelope(object sender, EventArgs<IMAP_Envelope> e)
            {
                IMAP_Envelope envelope = e.Value;
                if (envelope.From != null && !String.IsNullOrWhiteSpace(envelope.Subject))
                {
                    if (envelope.Subject.Trim().ToLower() == subject.ToLower())
                        result.Add(unseen_ids[currentIndex - 1]);
                }

                countToLoad--;
            }
        }

        private sealed class WrapperStep2 : WrapperStep
        {
            private int[] ids = null;
            public WrapperStep2(IMAP_Client client, int[] ids) :
                base(client)
            {
                this.ids = ids;
            }

            private int currentIndex = 0;
            private int countToLoad = 0;
            public List<ImapMessage> GetMessages()
            {
                currentIndex = 0;
                countToLoad = ids.Count();
                if (countToLoad <= 0)
                    return null;

                IMAP_SequenceSet sequence = new IMAP_SequenceSet();
                sequence.Parse(string.Join(",", ids));

                IMAP_Client_FetchHandler fetchHandler = new IMAP_Client_FetchHandler();
                fetchHandler.NextMessage += new EventHandler(fetchHandler_NextMessage);
                fetchHandler.Envelope += new EventHandler<EventArgs<IMAP_Envelope>>(fetchHandler_Envelope);
                fetchHandler.Body += new EventHandler<IMAP_Client_Fetch_Body_EArgs>(fetchHandler_Body);

                result = new List<ImapMessage>();
                client.Fetch(false, sequence, new IMAP_Fetch_DataItem[] { new IMAP_Fetch_DataItem_Envelope(), new IMAP_Fetch_DataItem_BodyPeek() }, fetchHandler);

                while (countToLoad > 0)
                {
                    Thread.Sleep(500);
                }

                return result;
            }
            private List<ImapMessage> result = null;
            ImapMessage currentMessage = null;

            void fetchHandler_Envelope(object sender, EventArgs<IMAP_Envelope> e)
            {
                currentMessage = new ImapMessage();
                currentMessage.Id = ids[currentIndex - 1];

                IMAP_Envelope envelope = e.Value;
                if (envelope.From != null && !String.IsNullOrWhiteSpace(envelope.Subject))
                {
                    currentMessage.Subject = envelope.Subject;
                }
            }

            private MemoryStream storeStream = null;

            void fetchHandler_Body(object sender, IMAP_Client_Fetch_Body_EArgs e)
            {
                storeStream = new MemoryStream();
                e.Stream = storeStream;

                e.StoringCompleted += new EventHandler(delegate(object sender2, EventArgs e2)
                {
                    storeStream.Position = 0;

                    // now convert memory stream into string
                    StreamReader streamReader = new StreamReader(storeStream);
                    string body = streamReader.ReadToEnd();

                    currentMessage.Body = body;
                    result.Add(currentMessage);

                    countToLoad--;
                });
            }

            void fetchHandler_NextMessage(object sender, EventArgs e)
            {
                currentIndex++;
            }
        }

        private IMAP_Client client;
        private ILog log;

        public ImapWrapper(ILog log)
        {
            this.log = log;
            client = new IMAP_Client();

            client.Connect(ConfigurationManager.AppSettings["imap_server"], Int32.Parse(ConfigurationManager.AppSettings["imap_port"]), Boolean.Parse(ConfigurationManager.AppSettings["imap_use_ssl"]));
            client.Login(ConfigurationManager.AppSettings["imap_email"], ConfigurationManager.AppSettings["imap_password"]);
            client.SelectFolder("INBOX");
        }

        private List<ImapMessage> imapMessages = null;

        public List<ImapMessage> GetUnseenMessages(string subject)
        {
            int[] unseen_ids = client.Search(false, "UTF-8", "unseen");
            
            if (unseen_ids.Count() <= 0)
                return null;

            try
            {

                // we want to load messages with specified subject only.
                // we don't want to load all the message bodies.
                // so we need to:
                // 1. make an ids array of all messages with specified subject, without loading their bodies
                // 2. load these messages completely

                WrapperStep1 step1 = new WrapperStep1(client, subject, unseen_ids);

                int[] ids = step1.GetIdsWithSubject();
                if (ids.Count() <= 0)
                    return null;

                WrapperStep2 step2 = new WrapperStep2(client, ids);
                return step2.GetMessages();
            }
            catch (Exception ex)
            {
                log.Write("Exception: " + ex.Message);
                return null;
            }
        }

        public void MarkSeen(ImapMessage imapMessage)
        {
            try
            {
                IMAP_SequenceSet sequence = new IMAP_SequenceSet();
                sequence.Parse(imapMessage.Id.ToString());
                client.StoreMessageFlags(false, sequence, IMAP_Flags_SetType.Add, IMAP_MessageFlags.Seen);
            }
            catch (Exception ex)
            {
                log.Write("Exception: " + ex.Message);
            }
        }

        public void Dispose()
        {
            client.Disconnect();
            client.Dispose();
        }
    }
}
